<script setup>
  import {nextTick, onMounted, ref, watch} from "vue";
  import { ElMessage } from "element-plus";
  import chatApi from "@/api/chat/index.js";
  import ResponseUtil from "@/utils/ResponseUtil.js";
  import LocalStoreUtil from "@/utils/LocalStoreUtil.js";
  import Loading from "@/components/common/Loading.vue";
  import { ArrowDownBold } from "@element-plus/icons-vue";
  import ChatMessage from "@/components/chat/ChatMessage.vue";
  import { initRouterUtil } from "@/utils/RouterUtil.js";

  const RouterUtil = initRouterUtil();


  // websocket 连接状态
  let webSocketStatus = ref(false);

  // 消息输入框 DOM 元素
  let chatMessageTextareaDom = ref(null);

  // 聊天窗口 DOM 元素
  let chatWindowDom = ref(null);

  // 全局加载效果，默认开启
  let globalLoading = ref(true);

  // 聊天窗口滚动高度固定（禁止自动滚动），默认关闭
  let chatWindowScrollTopFixed = ref(false);

  // 聊天窗口滚动过度动画，默认开启
  let chatWindowScrollAnimation = ref(true);

  // 聊天窗口静态高度
  let chatWindowHeight = 0;

  // 聊天窗口初始化
  let chatWindowInit = true;

  // 用户唯一标识
  let userUid = ref('');

  // 聊天室id
  let chatRoomId = ref(RouterUtil.getParams().chatRoomId);

  // 聊天室信息
  let chatRoomInfo = ref({
    chatRoomId: 0,
    chatRoomName: '',
  });

  let pageNo = ref(1); // 页码
  let pageSize = ref(30); // 每页条数
  let pages = ref(1); // 总页数
  let isNext = ref(false); // 是否有下一页
  let messageList = ref([]);

  let onlineList = ref([]);

  let selectLoading = ref(false);

  let message = ref('');

  // 消息发布加载状态，发布中时不允许重复发送
  let messagePublishLoading = ref(false);

  // dom 挂载
  onMounted(() => {
    // 判断用户是否登录
    if (!LocalStoreUtil.isLogin()) {
      ElMessage.error('访问聊天室需要先登录标签页');
      RouterUtil.replace('/login', '/chat/' + chatRoomId.value);
      return;
    }

    // 等待 dom 元素准备完成
    nextTick();

    // 初始化滚动监听
    initRollObserver();

    // 初始化 websocket
    initWebsocket();
  })

  // 监听 webSocket 连接状态
  watch(webSocketStatus, (newValue, oldValue) => {
    if (newValue === true) {
      // 初始化消息列表数据
      initMessageList();
    }
  });

  /**
   * 初始化 websocket 连接
   */
  const initWebsocket = async () => {
    // 获取用户唯一标识
    userUid.value = LocalStoreUtil.getUserUid();

    // 向服务器请求聊天室基本信息
    const chatRoomInfoResponse = await chatApi.getChatRoomInfo(chatRoomId.value);
    ResponseUtil.data(chatRoomInfoResponse).dispose(response => {
      if (response.code === '200') {
        chatRoomInfo.value = response.data;
      }
    })

    // 向服务器请求在线用户信息
    getOnlineList();

    // 创建客户端与服务器的 websocket 连接
    let socket = new WebSocket('ws://localhost:16636/websocket/chat/room/' + chatRoomInfo.value.chatRoomId + '/' + userUid.value);
    // let socket = new WebSocket('ws://120.46.171.159:16636/websocket/chat/room/' + chatRoomInfo.value.chatRoomId + '/' + userUid.value);

    // 连接创建完成的回调函数
    socket.onopen = () => {
      ElMessage.success('客户端与服务器建立连接完成，等待连接初始化');

      // 连接建立成功后，请求服务器进行后置初始化
      chatApi.initAfter(chatRoomInfo.value.chatRoomId).then(response => {
        ResponseUtil.data(response).dispose(response => {
          if (response.code === '200') {
            ElMessage.success('连接初始化完成，开始请求最近的消息记录');
            // 修改 websocket 连接状态，触发监听函数，向服务器发起请求，获取最近聊天记录
            webSocketStatus.value = true;
          }
        })
      })
    }

    // 服务器通过 websocket 向客户端发送消息时的回调函数
    socket.onmessage = (data) => {
      let websocketData = JSON.parse(data.data);
      switch (websocketData.type) {
        case 1:
          break;
        case 2:
          delete websocketData.type;
          messageList.value.push(websocketData);
          break;
        case 3:
          getOnlineList();
          break;
      }
    }

    // 服务器断开与服务器的 websocket 连接
    socket.onclose = () => {
      ElMessage.error('服务器与客户端的连接断开');
      // todo 尝试重新建立连接
    }

    // 发生了错误事件
    socket.onerror = () => {
      ElMessage.error('网络可能存在波动请您检查网络连接');
    }
  }

  /**
   * 初始化滚动监听
   */
  const initRollObserver = () => {

    // 监听到聊天窗口节点变化后的回调函数
    const callback = () => {
      // 判断是否为聊天窗口初始化时
      if (chatWindowInit) {
        // 设置延迟 610 毫秒后，获取高度，设定初始化进入时处于底部
        setTimeout(() => {
          chatWindowDom.value.scrollTop = chatWindowDom.value.scrollHeight;
          chatWindowInit = false;
        }, 600);
        return;
      }

      // 判断是否开启了聊天窗口滚动位置固定
      if (chatWindowScrollTopFixed.value) {
        // 重新计算高度
        if (chatWindowHeight > 0 && chatWindowHeight !== chatWindowDom.value.scrollHeight) {
          // 计算当前可滚动高度与记录的静态高度相差，将其设置为当前聊天窗口滚动位置，实现高度聊天窗口高度增加，但滚动位置不变的效果
          chatWindowDom.value.scrollTop = chatWindowDom.value.scrollHeight - chatWindowHeight;

          // 重置静态高度
          chatWindowHeight = 0;

          // 取消禁止聊天窗口滚动位置固定
          chatWindowScrollTopFixed.value = false;
          return;
        }
        return;
      }

      // 判断是否启用聊天窗口滚动过度动画
      if (chatWindowScrollAnimation.value) {
        // 滚动时添加过度动画效果
        chatWindowDom.value.scrollTo({
          top: chatWindowDom.value.scrollHeight,
          behavior: 'smooth'
        });
      } else {
        // 滚动时不添加过度动画效果
        chatWindowDom.value.scrollTop = chatWindowDom.value.scrollHeight;
      }
    }

    // 定义 MutationObserver 监听到目标的变化后触发的回调函数
    const observer = new MutationObserver(callback);

    // 设置回调函数的监听目标，监听目标的内容变化、动态属性变化、动态结构变化
    observer.observe(chatWindowDom.value, {
      childList: true, // 观察目标节点的子节点变化
      subtree: true,  // 观察整个子树的变化
      characterData: true // 观察文本节点变化
    });

  }

  /**
   * 初始化列表数据
   */
  const initMessageList = async () => {

    // 向服务器请求当前聊天室的消息
    chatApi.pageMessageByChatRoomId({
      pageNo: 1,
      pageSize: 30,
      chatRoomId: chatRoomId.value
    }).then(response => {
      // 根据请求结果做出相应处理
      ResponseUtil.data(response).dispose(response => {
        if (response.code === '200') {
          // 分页信息
          pageNo.value = response.data.pageNo;
          pageSize.value = response.data.pageSize;
          pages.value = response.data.pages;

          // 判断是否还有下一页
          isNext.value = pageNo.value < pages.value;

          // 判断当前聊天室是否有消息
          if (response.data.list === null || response.data.list <= 0) {
            ElMessage.success('当前聊天室过于安静了，快来进行聊天吧！');
          } else {
            // 将请求到的数据插入到页面显示的数据集合中，重新渲染页面
            messageList.value.unshift(...response.data.list);
            ElMessage.success('最近的消息记录加载完成');


            // 取消全局加载
            setTimeout(() => {
              globalLoading.value = false;
            }, 600);
          }

        } else {
          ElMessage.error('请求更多消息失败');
        }

      })
    })
  }

  /**
   * 获取更多消息数据
   */
  const pageMessage = (messageId) => {
    // 禁止聊天窗口自动滚动
    chatWindowScrollTopFixed.value = true;

    // 开启查询加载动画
    selectLoading.value = true;

    // 记录当前聊天窗口的可滚动总高度
    chatWindowHeight = chatWindowDom.value.scrollHeight;

    // 请求参数对象
    const req = {
      pageNo: pageNo.value,
      pageSize: pageSize.value,
      chatRoomId: chatRoomId.value,
      startMessageId: messageId
    }

    // 向服务器请求消息数据
    chatApi.pageMessageByChatRoomId(req).then(response => {

      // 根据请求结果做出相应处理
      ResponseUtil.data(response).dispose(response => {
        if (response.code === '200') {

          // 将请求到的数据插入到页面显示的数据集合中，重新渲染页面
          messageList.value.unshift(...response.data.list);

          // 分页信息
          pageNo.value = response.data.pageNo;
          pageSize.value = response.data.pageSize;
          pages.value = response.data.pages;

          // 判断是否还有下一页
          isNext.value = pageNo.value < pages.value;

          // 关闭查询加载动画
          selectLoading.value = false;
        } else {
          ElMessage.error('请求更多消息失败');
        }
      })

    })
  }

  const messagePublish = async (messageInner) => {
    if (messagePublishLoading.value) {
      ElMessage.warning('消息正在发送中');
      return;
    }

    // 参数校验
    if (!messageInner || !messageInner.replaceAll(' ', '')) {
      ElMessage.warning('发送消息不能为空');
      return;
    }

    // 消息发布加载状态触发，禁止加载期间重复提交
    messagePublishLoading.value = true;

    // 消息发布前置操作，获取本次消息id
    const response = await chatApi.execBeforeMessagePublishToChatRoom(
        {
          chatRoomId: chatRoomId.value
        }
    );

    const req = {
      chatRoomId: chatRoomId.value,
      messageId: response.data.value,
      messageBody: messageInner,
      publishTime: new Date().getTime()
    }

    // 请求服务器，发布消息到聊天室
    chatApi.saveMessageToChatRoom(req).then(response => {
      ResponseUtil.data(response).dispose(response => {
        if (response.code === '200' && response.data.value) {
          ElMessage.success('消息发送成功');
          message.value = '';

          // 消息发布加载状态重置
          messagePublishLoading.value = false;
        }
      })
    })

    // 将消息内容添加到当前用户的聊天窗口中
    messageList.value.push({
      chatRoomId: chatRoomId.value,
      messageId: response.data.value,
      publishTime: new Date().getTime(),
      message: req.messageBody,
      userUid: userUid.value,
      nickName: LocalStoreUtil.getUserInfo().nickName,
      userId: LocalStoreUtil.getUserInfo().id,
      avatar: LocalStoreUtil.getUserInfo().avatar
    });
  }
  const handleEnterKey = (event) => {
    if (event.ctrlKey || event.metaKey) {
      const start = event.target.selectionStart;
      const end = event.target.selectionEnd;

      // 在光标位置插入换行符
      message.value = message.value.slice(0, start) + '\n' + message.value.slice(end);

      // 更新光标位置
      nextTick(() => {
        event.target.selectionStart = event.target.selectionEnd = start + 1;
      })
      return;
    }
    // 发送消息
    messagePublish(message.value);
  }

  // 清空消息
  const cancelMessage = () => {
    message.value = '';
    chatMessageTextareaDom.value.focus();
  }

  // 禁止页面手动进行大小调整
  const preventZoom = (event) => {
    // 阻止默认的浏览器缩放行为
    if (event.ctrlKey) {
      event.preventDefault();
    }
  }

  const getOnlineList = () => {
    chatApi.getOnlineList(chatRoomInfo.value.chatRoomId).then(response => {
      ResponseUtil.data(response).dispose(response => {
        if (response.code === '200') {
          onlineList.value = response.data.list;
        } else {
          onlineList.value = [];
        }
      })
    })
  }

</script>

<template>
  <div @wheel.prevent="preventZoom($event)" :class="['chat-loading-basic', globalLoading ? '':'loading-hide-animation']">
    <div class="warning">
      <div class="chat-loading">
        <Loading style="transform:scale(3)"/>
      </div>
      <div class="chat-loading-text">正在连接聊天室，请稍后！</div>
    </div>
  </div>

  <div :class="['chat-basic', !globalLoading ? 'loading-show-animation':'loading-hide-animation']">
    <div class="header">
      <div class="header-title">{{ chatRoomInfo.chatRoomName }}</div>
    </div>
    <div class="chat-body">
      <div class="dialogue" ref="chatWindowDom">
        <div class="select-loading" v-if="selectLoading">
          <Loading/>
        </div>
        <div class="select-message" v-if="isNext && selectLoading === false" @click.stop="pageMessage(messageList[0].messageId)">
          <div class="select-text">查看更多消息</div>
          <div class="select-icon">
            <el-icon>
              <ArrowDownBold />
            </el-icon>
          </div>
        </div>
        <ChatMessage v-for="(item, index) in messageList" :key="item.messageId" :data="item" :source-user-uid="userUid"></ChatMessage>
      </div>
      <div class="notice"></div>
    </div>
    <div class="footer">
      <div class="message">
        <div class="message-input-basic">
          <textarea v-model="message" ref="chatMessageTextareaDom"
                    @keydown.enter.prevent="handleEnterKey($event)"></textarea>
        </div>
        <div class="button">
          <div class="btn-basic btn-publish" @click="messagePublish(message)">发送</div>
          <div class="btn-basic btn-cancel" @click="cancelMessage">取消</div>
        </div>
      </div>
      <div class="on-line">
        <div class="not-online" v-if="onlineList.length <= 0">
          当前聊天室空无一人~
        </div>
        <div class="account-list-basic" v-if="onlineList.length > 0">
          <div class="account-item" v-for="item in onlineList" :key="item.userUid">
            <div class="account-avatar">
              <div class="text-avatar">{{ item.nickName.substring(0, 1) }}</div>
            </div>
            <div class="account-nickname">{{ item.nickName }}</div>
          </div>
        </div>
        <div class="account-count">
          实时在线人数：{{ onlineList.length }} 人
        </div>
      </div>
    </div>
  </div>
</template>

<style scoped>

  .chat-loading-basic {
    width: 100vw;
    height: 100vh;
    position: fixed;
    background-color: white;
    z-index: 1;
    backdrop-filter: blur(10px);
    display: flex;
    justify-content: center;
    align-items: center;
  }
  .chat-loading {
    width: 300px;
    height: 100px;
    display: flex;
    justify-content: center;
    align-items: center;
  }
  .chat-loading-text {
    display: flex;
    justify-content: center;
    height: 300px;
    user-select: none;
  }

  .chat-basic {
    height: 100vh;
    width: 100vw;
  }

  .header {
    width: 100vw;
    height: 60px;
    background-color: white;
    box-shadow: 0 0 10px 5px rgba(0, 0, 0, .1);
    position: relative;
  }
  .header-title {
    margin-left: 50px;
    height: 100%;
    display: flex;
    align-items: center;
    font-size: 15px;
  }

  .chat-body {
    width: 100vw;
    height: calc(78vh - 60px);
    display: flex;
  }

  .dialogue {
    width: 85vw;
    height: calc(100%);
    background-color: rgb(238, 238, 238);
    overflow-y: scroll;
    position: relative;
  }

  .select-loading {
    width: 136px;
    height: 24px;
    position: relative;
    left: calc(50% - 136px);
    display: flex;
    justify-content: center;
    align-items: center;
    margin-top: 8px;
  }

  .select-message {
    width: 136px;
    height: 24px;
    position: relative;
    left: calc(50% - 136px);
    display: flex;
    justify-content: space-around;
    align-items: center;
    margin-top: 8px;
  }
  .select-text {
    cursor: pointer;
  }
  .select-icon {
    cursor: pointer;
    border-radius: 25px;
    background-color: rgb(200, 200, 200, 0.7);
    width: 18px;
    height: 18px;
    display: flex;
    justify-content: center;
    align-items: center;
    font-size: 10px;
  }

  .notice {
    width: 15vw;
    height: 100%;
    background-color: #b3e19d;
    box-shadow: 0 5px 15px 5px rgba(0, 0, 0, .1);
  }

  .footer {
    width: 100vw;
    height: calc(22vh - 1px);
    background-color: rgb(248, 248, 248);
    border-top: 1px solid black;
    display: flex;
    justify-content: center;
  }

  .message {
    width: 85vw;
    height: 100%;
    box-shadow: 5px 0 15px 5px rgba(0, 0, 0, .1);
  }

  .message-input-basic {
    width: 100%;
    height: calc(100% - 36px);
    border-bottom: 1px solid rgba(102, 102, 102, 0.5);
    display: flex;
    justify-content: center;
    align-items: center;
  }

  .message-input-basic textarea {
    padding: 10px;
    width: calc(100% - 20px);
    height: calc(100% - 20px);
    border: none;
    outline: none;
    font-size: 15px;
  }

  .button {
    width: 100%;
    height: 35px;
    display: flex;
    align-items: center;
    justify-content: flex-end;
  }
  .btn-basic {
    width: 100px;
    height: 24px;
    display: flex;
    justify-content: center;
    align-items: center;
    font-size: 14px;
    transition: background-color .2s;
    cursor: pointer;
    user-select: none;
    border-radius: 3px;
    margin: 0 10px;
  }

  .btn-publish {
    background-color: rgb(0, 127, 255);
    color: white;
  }
  .btn-publish:hover {
    background-color: rgb(78, 173, 246);
  }

  .btn-cancel {
    background-color: rgba(200, 200, 200, .7);
  }
  .btn-cancel:hover {
    background-color: rgb(180, 180, 180);
  }

  .on-line {
    width: calc(15vw - 1px);
    border-left: 1px solid rgba(102, 102, 102, .5);
    height: 100%;
  }
  .account-list-basic {
    width: 100%;
    height: calc(100% - 35px - 1px - 10px);
    overflow-y: scroll;
    background-color: white;
    padding: 5px 0;
  }
  .account-item {
    width: 100%;
    height: 42px;
    display: flex;
    align-items: center;
  }
  .account-avatar {
    width: 36px;
    height: 36px;
    overflow: hidden;
    border-radius: 50%;
    margin-left: 6px;
    float: left;
  }
  .account-avatar .text-avatar {
    width: 100%;
    height: 100%;
    display: flex;
    justify-content: center;
    align-items: center;
    font-size: 18px;
    color: white;
    background-color: rgb(188, 188, 188);
  }

  .account-nickname {
    width: calc(100% - 12px - 6px - 36px);
    margin-left: 12px;
    height: 41px;
    border-bottom: 1px solid rgba(102, 102, 102, .6);
    float: left;
    display: flex;
    align-items: center;
    color: rgb(68, 68, 68);
    text-indent: 8px;
  }
  .account-item:last-of-type .account-nickname {
    border: none;
    height: 42px;
  }

  .not-online {
    width: 100%;
    height: calc(100% - 35px - 1px);
    background-color: white;
    display: flex;
    align-items: center;
    justify-content: center;
    color: rgba(68, 68, 68);
  }

  .account-count {
    width: 100%;
    height: 35px;
    border-top: 1px solid rgba(102, 102, 102, .2);
    background-color: white;
    display: flex;
    align-items: center;
    font-size: 14px;
    text-indent: 12px;
    color: rgb(58, 58, 58);
  }

  .loading-show-animation {
    animation-name: showAnimation;
    animation-duration: 1.2s;
    animation-fill-mode: forwards;
  }
  .loading-hide-animation {
    display: none;
    animation-name: hideAnimation;
    animation-duration: 0.4s;
    animation-fill-mode: forwards;
  }

  @keyframes showAnimation {
    from {
      opacity: 0;
    }
    to {
      opacity: 1;
    }
  }
  @keyframes hideAnimation {
    from {
      opacity: 1;
      display: flex;
    }
    to {
      opacity: 0;
    }
  }
</style>